/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package edos.prm.role;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import edos.prm.activity.Activity;
import edos.prm.activity.Operation;
import edos.prm.activity.ActivityManager.PrmActivityManager;
import edos.prm.artifact.UnregisteredArtifactType;
import edos.prm.artifact.ArtifactManager.PrmArtifactManager;
import edos.prm.engine.PRM;
import edos.prm.engine.PrmSecurityManager;
import edos.prm.project.Project;
import edos.prm.util.Time;
import edos.prm.util.VersionNumber;

/**
 * The {@code RoleManager} is responsible for managing and assigning roles to
 * actors.
 * 
 * @version 1.0
 */
public interface RoleManager extends Activity {

	/**
	 * Associate a set of {@code Right}s with a new role
	 * 
	 * @param name the name of the new role
	 * @param description a description of this role
	 * @param ops a set of operations that the role permits
	 * @return a new role object
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public Role newRole(String name, String description, Set<Operation> ops)
	throws RoleException;

	/**
	 * Verifies if a Role is authorized to call a edos.pmi method
	 * 
	 * @param role the Role whose rights have to be checked
	 * @param operation the edos.pmi method whose access by the Role is checked
	 * @return true if the Role is authorized to call the operation
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public boolean isAuthorized(Role role, Operation operation);

	/**
	 * Returns a copy of the set of Rights that this role permits
	 * 
	 * @param role the Role whose authorzations are being retrieved
	 * @return a Set of Rights that represent this role.
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public Set<Right> getAuthorizations(Role role);

	/**
	 * Returns all the Roles authorized to call a PRM operation
	 * 
	 * @param operation for which authorized Roles are retrieved
	 * @return a copy of the set of Roles authorized to call this operation
	 */
	public Set<Role> getAuthorizedRoles(Operation operation);

	/**
	 * Returns all Projects using a specific Role
	 * 
	 * @param role the Role that is supposed to be shared
	 * @return a copy of the set of Projects using the Role
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public Set<Project> getProjects(Role role);

	/**
	 * Returns all existing Roles in the PRM information system
	 * 
	 * @return a copy of the set of existing Roles
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public Set<Role> getRoles();

	/**
	 * Returns Roles of a Project
	 * 
	 * @return a copy of the set of Roles of a Project
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public Set<Role> getRoles(Project project);

	/**
	 * Associates a {@code Role} with a {@code Project}.
	 * 
	 * @param role the role to bind to a project
	 * @param project the project in which the role is activated
	 * @throws RoleException if the caller does not have a role to call this
	 * operation.
	 */
	public void associateRole(Role role, Project project);

	/**
	 * A default Java-based implementation of the {@code RoleManager}.
	 */
	final static class PrmRoleManager implements RoleManager {

		private static PrmRoleManager instance;
		private Time creationDate;
		private HashSet<Role> roleCache;
		private HashMap<Role, HashSet<Project>> roleToProjectMap;
		private static PrmArtifactManager pam;

		public static PrmRoleManager getInstance() {
			return instance;
		}

		static {
			instance = new PrmRoleManager();
			pam = (PrmArtifactManager) PRM.getArtifactManager();
			pam.registerArtifactType(Role.PrmRole.class);
		}

		private PrmRoleManager() {
			this.roleCache = new HashSet<Role>();
			this.creationDate = new Time();
			this.roleToProjectMap = new HashMap<Role, HashSet<Project>>();
			PrmActivityManager.getInstance().register(this);
		}

		public Set<Right> getAuthorizations(Role role) {
			PrmSecurityManager.roleCheck("getAuthorizations", getClass(), Role.class);
			return role.getAllRights();
		}

		public Set<Role> getAuthorizedRoles(Operation operation) {
			PrmSecurityManager.roleCheck("getAuthorizedRoles", getClass(), Operation.class);
			HashSet<Role> result = new HashSet<Role>();
			for ( Role role : this.roleCache ) {
				Set<Right> rights = role.getAllRights();
				for ( Right r : rights ) {
					Operation[] ops = r.getOperations();
					for ( Operation op : ops )
						if ( op.equals(operation) )
							result.add(role);
				}
			}
			return result;
		}

		public Set<Project> getProjects(Role role) {
			PrmSecurityManager.roleCheck("addProjects", getClass(), Role.class);
			HashSet<Project> map = this.roleToProjectMap.get(role);
			return map;
		}

		public Set<Role> getRoles() {
			PrmSecurityManager.roleCheck("getRoles", getClass());
			HashSet<Role> result = new HashSet<Role>();
			result.addAll(roleCache);
			return result;
		}

		public Set<Role> getRoles(Project project) {
			PrmSecurityManager.roleCheck("getRoles", getClass(), Project.class);
			HashSet<Role> result = new HashSet<Role>();
			for ( Role role : this.roleToProjectMap.keySet() ) {
				HashSet<Project> map = this.roleToProjectMap.get(role);
				if ( map != null )
					if ( map.contains(project) )
						result.add(role);
			}
			return result;
		}

		public boolean isAuthorized(Role role, Operation operation) {
			PrmSecurityManager.roleCheck("isAuthorized", getClass(), new Class[] {Role.class, Operation.class});
			Set<Right> heldRights = role.getAllRights();
			for ( Right right : heldRights ) {
				Operation[] allowedOps = right.getOperations();
				for ( Operation op : allowedOps )
					if ( op.equals(operation) )
						return true;
			}
			return false;
		}

		public Time getCreationDate() {
			return this.creationDate;
		}

		public String getDescription() {
			return "Default Implementation of the Role Manager";
		}

		public VersionNumber getVersion() {
			return PRM.getPRMVersion();
		}

		public Role newRole(String name, String description, Set<Operation> ops) {
			PrmSecurityManager.roleCheck("newRole", getClass(), new Class[] {String.class, String.class, Set.class});
			Role role = null;
			try {
				role = (Role) pam.newArtifact(Role.PrmRole.class,
						PRM.getTaskManager().currentTask().getActor(),
						name, description, Role.ROOT_ROLE, ops);
			} catch (UnregisteredArtifactType e) {
				e.printStackTrace();
			}
			this.roleCache.add(role);
			return role;
		}

		public void associateRole(Role role, Project project) {
			PrmSecurityManager.roleCheck("assignRole", getClass(), new Class[] {Role.class, Project.class});
			HashSet<Project> map = this.roleToProjectMap.get(role);
			if ( map == null )
				map = new HashSet<Project>();
			map.add(project);
			this.roleToProjectMap.put(role, map);
		}
	}
}
